﻿/*
 * This software is the confidential and proprietary
 * information of NetBay
 * Such confidential information should not be disclosed
 * NetBay copyright(c) 2010
 */

#region Using
using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using NetBay.Core.Silverlight.ComponentModel;
using Rio.Framework.WCFService.RioWcfServices;
using NetBay.Core.Silverlight.Log;
#endregion

namespace Rio.Framework.WCFService
{
    public class PostDocument:IDisposable
    {
        #region Properties
        /// <summary>
        /// Reference to document
        /// </summary>
        public Guid Guid
        {
            get { return m_sGuid; }
        }
        /// <summary>
        /// Size of onr slice document
        /// </summary>
        public long SizeOfSlice
        {
            get { return m_lSizeOfSlice; }
        }
        /// <summary>
        /// User Id
        /// </summary>
        public long UserId
        {
            get { return m_lUserId; }
        }
        /// <summary>
        /// File to Post
        /// </summary>
        public FileInfo FileInfo
        {
            get { return m_oFileInfo; }
        }
        /// <summary>
        /// Total of slice to send
        /// </summary>
        public long TotalSlices
        {
            get { return m_lTotalSlices; }
            set { m_lTotalSlices = value; }
        }
        /// <summary>
        /// Exception fired in one send of slice
        /// </summary>
        public Exception ExceptionInSlice
        {
            get { return m_oExceptionInSlice; }
            set { m_oExceptionInSlice = value; }
        }
        /// <summary>
        /// 
        /// </summary>
        public string DestinationFullPath
        {
            get { return m_sDestinationRelativePath; }
            set { m_sDestinationRelativePath = value; }
        }


        #endregion

        #region Attributes
        /// <summary>
        /// Reference to document
        /// </summary>
        private Guid m_sGuid;
        /// <summary>
        /// User Id
        /// </summary>
        private long m_lUserId;
        /// <summary>
        /// Total of slice to send
        /// </summary>
        private long m_lTotalSlices;
        /// <summary>
        /// Size of one slice document
        /// </summary>
        private long m_lSizeOfSlice;
        /// <summary>
        /// File to Post
        /// </summary>
        private FileInfo m_oFileInfo;
        /// <summary>
        /// Exception fired in one send of slice
        /// </summary>
        private Exception m_oExceptionInSlice;
        /// <summary>
        /// Indicate if we must wait the end of operation
        /// </summary>
        private bool m_bAsynchronousMode;
        /// <summary>
        /// Thumbnail of the document
        /// </summary>
        public ImageRio m_oThumnail;
        /// <summary>
        /// Relative file path on the remote store
        /// </summary>
        private string m_sDestinationRelativePath;
        /// <summary>
        /// Store path
        /// </summary>
        private string m_sStoragePath;
        /// <summary>
        /// 
        /// </summary>
        private bool m_bOverwrite;

        protected FileStream m_oFileStream;
        private int m_iSliceCpt = 0;
        protected PostDocument m_oPostDocument;
        protected List<WaitHandle> m_oFinishedWaitHandles;
        protected Action<int, string, ImageRio> m_oOnProgress = null;
        protected List<FileSlice> m_oSliceList;
        private long m_lNumberOfSlicesToRun = 0;
        private long m_lDone = 0;
        private Object m_oLock = new Object();

        #endregion

        #region Events
        /// <summary>
        /// The delagate to handle the 'ProgressEventHandler'
        /// </summary>
        /// <param name="p_oSender">The sender</param>
        /// <param name="p_oProgressEH">the ProgressEventHandler</param>
        public delegate void ProgressEventHandler(object p_oSender, ProgressEventArgs p_oProgressEH);

        /// <summary>
        /// Event to notify the progress of Process
        /// </summary>
        public event ProgressEventHandler Progress;

        /// <summary>
        /// Fire a 'Progress' event to any registered client
        /// </summary>
        /// <param name="p_oOnProgress"></param>
        /// <param name="p_dPercentageDone">Percentage of done job</param>
        /// <param name="p_szErrorMessage">Error message if any</param>
        private void OnProgress(Action<int, string, ImageRio> p_oOnProgress, double p_dPercentageDone, string p_szErrorMessage, ImageRio p_oThumbNail)
        {
            lock (this)
            {
                //Logger.Write("(Name<" + FileInfo.Name + "> p_dPercentageDone<" + p_dPercentageDone + "> p_szErrorMessage<" + p_szErrorMessage + ">", Logger.Level.Verbose);
                if (p_oOnProgress != null)
                {
                    SmartDispatcher.BeginInvoke(p_oOnProgress, (int)(p_dPercentageDone * 100), p_szErrorMessage, p_oThumbNail);
                }
                else if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs((int)(p_dPercentageDone * 100), p_szErrorMessage, p_oThumbNail));
                }
            }
        }

        /// <summary>
        /// Fire a 'Progress' event to any registered client
        /// </summary>
        /// <param name="p_oOnProgress"></param>
        /// <param name="p_dPercentageDone">Percentage of done job</param>
        private void OnProgress(Action<int, string, ImageRio> p_oOnProgress, double p_dPercentageDone, ImageRio p_oThumbNail)
        {
            OnProgress(p_oOnProgress, p_dPercentageDone, string.Empty, p_oThumbNail);
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public PostDocument(int mITotalSlices)
            : this(Guid.Empty, 0, null, 0)
        {
            m_lTotalSlices = mITotalSlices;
        }

        /// <summary>
        /// Rich constructor
        /// </summary>
        /// <param name="p_sGuid">Reference to document</param>
        /// <param name="p_lUserId">User ID</param>
        /// <param name="p_oFileInfo">Information of file to post</param>
        /// <param name="p_lSizeOfSlice">Size of onr slice document</param>
        public PostDocument(Guid p_sGuid, long p_lUserId, FileInfo p_oFileInfo, long p_lSizeOfSlice, bool p_bAsynchronousMode = true, string p_sDestinationFullPath = "", string p_sStoragePath = "", bool p_bOverwrite = false)
        {
            m_sGuid = p_sGuid;
            m_lUserId = p_lUserId;
            m_oFileInfo = p_oFileInfo;
            m_lSizeOfSlice = p_lSizeOfSlice;
            m_bAsynchronousMode = p_bAsynchronousMode;
            m_sDestinationRelativePath = p_sDestinationFullPath;
            m_sStoragePath = p_sStoragePath;
            m_bOverwrite = p_bOverwrite;
        }

        

        #endregion

        #region Publics
        /// <summary>
        /// Process a 'PostDocument'
        /// </summary>
        /// <returns>true(process lauch correctly) otherwise false</returns>
        public Thread Do(Action<int, string, ImageRio> p_oOnProgress = null)
        {
            try
            {
                Thread l_oThread = new Thread(l_oThis =>
                {
                    m_oPostDocument = (PostDocument)l_oThis;
                    m_oOnProgress = p_oOnProgress;
                    try
                    {
                        // Check
                        if (m_oPostDocument.SizeOfSlice != 0 && m_oPostDocument.SizeOfSlice < int.MaxValue)
                        {
                            // Open the file as read only
                            m_oFileStream = m_oPostDocument.FileInfo.OpenRead();
                            long l_lLengthOfFile = m_oFileStream.Length;

                            // Calculate the number of slice
                            m_oPostDocument.TotalSlices = (l_lLengthOfFile / m_oPostDocument.SizeOfSlice + 1);
                            
                            m_lNumberOfSlicesToRun = m_oPostDocument.TotalSlices;
                            
                            // Read the source file into a byte array.
                            byte[] l_oOneRawBuffer = new byte[m_oPostDocument.SizeOfSlice];
                            
                            // One event is used for each Fibonacci object
                            m_oFinishedWaitHandles = new System.Collections.Generic.List<WaitHandle>();
                                                       
                            int l_iSliceReaded = 0;
                            byte[] l_oOneSliceToSend;
                            FileSlice l_oF;
                            if (m_oPostDocument.TotalSlices > 1)
                            {
                                //Initiate all slices and affect the ManualResetEvent to the finished list
                                m_oSliceList = new System.Collections.Generic.List<FileSlice>();
                                for (int i = 0; i < m_oPostDocument.TotalSlices; i++)
                                {
                                    l_oF = new FileSlice();
                                    m_oFinishedWaitHandles.Add(l_oF.Notify());
                                    m_oSliceList.Add(l_oF);
                                }

                                for (int l_iSliceId = 0; l_iSliceId < m_oPostDocument.TotalSlices - 1; l_iSliceId++)
                                {
                                    //Break when the number of concurrent slice in the pool is reached
                                    if (l_iSliceId > (ServiceManager.UploadThreadPoolMaxThreads -1)) 
                                        break;

                                    l_iSliceReaded = m_oFileStream.Read(l_oOneRawBuffer, 0, (int)m_oPostDocument.SizeOfSlice);

                                    // Break when the end of the file is reached.
                                    if (l_iSliceReaded == 0)
                                        break;

                                    // We have a buffer whith a maximum slice amount of byte
                                    l_oOneSliceToSend = new byte[l_iSliceReaded];
                                    Buffer.BlockCopy(l_oOneRawBuffer, 0, l_oOneSliceToSend, 0, l_iSliceReaded);

                                    m_oSliceList[l_iSliceId].Prepare(this, l_iSliceId, false, l_oOneSliceToSend, m_sDestinationRelativePath, m_sStoragePath, m_bOverwrite);
                                    m_oSliceList[l_iSliceId].Finalized += new EventHandler(f_Finalized);
                                    ThreadPool.QueueUserWorkItem(m_oSliceList[l_iSliceId].ThreadPoolCallback, l_iSliceId);
                                    m_iSliceCpt++;
                                    m_lNumberOfSlicesToRun--;

                                }

                            }
                            else
                            {
                                //Only one slice to manage
                                l_oF = new FileSlice();
                                l_iSliceReaded = m_oFileStream.Read(l_oOneRawBuffer, 0, (int)m_oPostDocument.SizeOfSlice);
                                m_oFinishedWaitHandles = new System.Collections.Generic.List<WaitHandle>();
                                l_oOneSliceToSend = new byte[l_iSliceReaded];
                                Buffer.BlockCopy(l_oOneRawBuffer, 0, l_oOneSliceToSend, 0, l_iSliceReaded);
                                m_oFinishedWaitHandles.Add(l_oF.Notify());
                                l_oF.Finalized += new EventHandler(f_Finalized);
                                l_oF.Prepare(this, (int)(m_oPostDocument.TotalSlices - 1), true, l_oOneSliceToSend, m_sDestinationRelativePath, m_sStoragePath, m_bOverwrite);
                                ThreadPool.QueueUserWorkItem(l_oF.ThreadPoolCallback, m_oPostDocument.TotalSlices - 1);
                            }

                            //This algorithme beacause the waitall cannot wait above 64 threads in the queue
                            if (m_oFinishedWaitHandles.Count >= 64)
                            {
                                int done = m_oFinishedWaitHandles.Count;
                                var i = 0;
                                var read = 0;
                                do{
                                    if (done >= 64)
                                    {
                                        done -= 64;
                                        WaitHandle[] t = m_oFinishedWaitHandles.GetRange(read, 63).ToArray();
                                        read += 64;
                                        WaitHandle.WaitAll(t);
                                        
                                    }
                                    else
                                    {
                                        WaitHandle[] t = m_oFinishedWaitHandles.GetRange(read, done).ToArray();
                                        WaitHandle.WaitAll(t);
                                        done = 0;
                                    }
                                    i++;
                                } while (done > 0);
                            }
                            else
                            {
                                WaitHandle.WaitAll(m_oFinishedWaitHandles.ToArray());
                            }
                            
                        }
                        else
                        {
                            throw new Exception("SizeOfSlice<" + SizeOfSlice + "> is invalid");
                        }
                        
                    }
                    catch (Exception)
                    {
                        //Logger.Write("PostDocument.Do -> EXCEPTION:" + Environment.NewLine + ex.Message, Logger.Level.Error);
                        if (m_oPostDocument != null && !string.IsNullOrEmpty(m_oPostDocument.FileInfo.Name))
                        {
                            OnProgress(p_oOnProgress, -1, m_oPostDocument.FileInfo.Name, null);
                        }
                        OnProgress(p_oOnProgress, -1, string.Empty, null);
                    }
                });
                // Launch the process
                l_oThread.IsBackground = true;
                l_oThread.Start(this);
                if (m_bAsynchronousMode == false)
                {
                    l_oThread.Join();
                }
                return l_oThread;
            }
            catch (Exception ex)
            {
                throw new Exception("Unable to 'Launch a PostDocument'", ex);
            }
        }
        
        /// <summary>
        /// Event raised each time a slice ends
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void f_Finalized(object sender, EventArgs e)
        {
            lock (m_oLock)
            {
                FileSlice l_oCurrentSlice = (FileSlice)sender;

                if (!String.IsNullOrEmpty(l_oCurrentSlice.ErrorMessage))
                {
                    OnError(l_oCurrentSlice.ErrorMessage);
                }
                else
                {
                    m_lDone++;
                    Thread.Sleep(ServiceManager.UploadThreadInterval);
                    
                    int l_iSliceReaded;
                    byte[] l_oOneRawBuffer = new byte[m_oPostDocument.SizeOfSlice];
                    byte[] l_oOneSliceToSend;

                    if (m_lNumberOfSlicesToRun > 1)
                    {
                        // We have a buffer whith a maximum slice amount of byte
                        l_iSliceReaded = m_oFileStream.Read(l_oOneRawBuffer, 0, (int)m_oPostDocument.SizeOfSlice);
                        l_oOneSliceToSend = new byte[l_iSliceReaded];
                        Buffer.BlockCopy(l_oOneRawBuffer, 0, l_oOneSliceToSend, 0, l_iSliceReaded);
                        if (l_oOneSliceToSend.Length != 0)
                        {
                            m_oSliceList[m_iSliceCpt].Prepare(this, m_iSliceCpt, false, l_oOneSliceToSend, m_sDestinationRelativePath, m_sStoragePath, m_bOverwrite);
                            m_oSliceList[m_iSliceCpt].Finalized += new EventHandler(f_Finalized);
                            ThreadPool.QueueUserWorkItem(m_oSliceList[m_iSliceCpt].ThreadPoolCallback, m_iSliceCpt);
                            m_iSliceCpt++;
                        }
                        m_lNumberOfSlicesToRun--;
                    }
                    else
                    {
                        //Call the latest slice
                        if (m_lTotalSlices - m_lDone == 1)
                        {
                            l_iSliceReaded = m_oFileStream.Read(l_oOneRawBuffer, 0, (int)m_oPostDocument.SizeOfSlice);
                            m_oFinishedWaitHandles = new System.Collections.Generic.List<WaitHandle>();
                            l_oOneSliceToSend = new byte[l_iSliceReaded];
                            Buffer.BlockCopy(l_oOneRawBuffer, 0, l_oOneSliceToSend, 0, l_iSliceReaded);

                            m_oSliceList[m_iSliceCpt].Prepare(this, (int)(m_oPostDocument.TotalSlices - 1), true, l_oOneSliceToSend, m_sDestinationRelativePath, m_sStoragePath, m_bOverwrite);
                            m_oSliceList[m_iSliceCpt].Finalized += new EventHandler(f_Finalized);
                            ThreadPool.QueueUserWorkItem(m_oSliceList[m_iSliceCpt].ThreadPoolCallback, m_oPostDocument.TotalSlices - 1);

                        }
                        else if (m_lTotalSlices - m_lDone == 0)
                        {
                            // Close to access file
                            m_oFileStream.Close();

                            // Notify clients the end of progression
                            OnProgress(m_oOnProgress, 1.0, m_oThumnail);
                        }
                    }
                }

                l_oCurrentSlice.Dispose();
            }
            
        }

        /// <summary>
        /// This method is called when a slice is on error.
        /// It closes all the slice to give the hand to the main thread.
        /// </summary>
        /// <param name="p_sErrorMessage"></param>
        private void OnError(String p_sErrorMessage)
        {
            if (m_oSliceList != null)
            {
                foreach (FileSlice f in m_oSliceList)
                {
                    f.End();
                    f.Dispose();
                }
            }
            
            // Close to access file
            m_oFileStream.Close();

            // Notify clients the end of progression
            OnProgress(m_oOnProgress, 1.0, p_sErrorMessage, m_oThumnail);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {

            }
            //m_oFileInfo = null;
            //m_oExceptionInSlice = null;
            //m_oThumnail = null;
            //l_fsfileStream = null;
            //m_oPostDocument = null;
            //finished = null;
            //m_oSliceList = null;
        }
        #endregion

    }
}
